/**
 * @Copyright:Copyright (c) 1992 - 2015
 * @Company: TengTong
 */
package com.tengtong.duang.commons.utils;

import java.io.File;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Collections;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import com.tengtong.duang.commons.base.ClassFilter;
import com.tengtong.duang.commons.collections.ArrayWrapper;

/**
 * 操纵包下的所有 java class 文件 （也包括 jar 包）
 *
 * @author jai
 * @since: v1.0
 */
public class PackageUtils {
	
	/**
	 * 从包中获取所有的 Class。包括从路径和 Jar 文件中获取。
	 *
	 * @param packageName  包名
	 * @return
	 * @throws Exception 
	 */
	public static Set<Class<?>> getClassesInPackage(String packageName) throws Exception{
		return getClassesInPackage(packageName, null);
	}
	
	/**
	 * 提供一个 class 过滤器，用来过滤数据
	 *
	 * @param packageName
	 * @param filter
	 * @return
	 * @throws Exception
	 */
	public static Set<Class<?>> getClassesInPackage(String packageName, ClassFilter filter) throws Exception{
		Set<Class<?>> results = new LinkedHashSet<Class<?>>();  
		// 是否循环迭代  
		boolean recursive = true;  
		// 获取包的名字 并进行替换  
		String packageDirName = packageName.replace('.', '/');  
		// 定义一个枚举的集合 并进行循环来处理这个目录下的things  
		try {  
			List<URL> dirs = getDefaultResources(packageDirName);
			
			dirs.forEach(url->getClassInDirectory(packageName, filter, results, recursive, url));
			
		}catch(Exception e){
			throw e;
		}
		return results;
	}

	/**
	 *
	 * @param packageDirName
	 * @return
	 * @throws IOException
	 */
	private static List<URL> getDefaultResources(String packageDirName) throws IOException {
		Enumeration<URL> dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
		return Collections.list(dirs);
	}
	
	/**
	 * 单个文件（可能是 .class 也可能是 .jar）中获取 class
	 *
	 * @param packageName   包名称
	 * @param filter        可以过渡掉那些不需要的
	 * @param results       结果列表       
	 * @param recursive     是否做递归（目录递归）
	 * @param url           要分析处理的文件
	 */
	private static void getClassInDirectory(String packageName, ClassFilter filter, Set<Class<?>> results, boolean recursive, URL url){
		try{
			if ("file".equals(url.getProtocol())) {  
				// 获取包的物理路径  
				String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
				findClassesInPackageByFile(filePath, packageName, recursive, results, filter);
			}else if ("jar".equals(url.getProtocol())) {					
				findClassesInPackageByJarFile(url, packageName, recursive, results, filter);
			}
		}catch(Exception e){
			FrameworkLogger.error("[Package Utils]==> 读取文件[" + url.getFile() + "]失败，异常：", e);
		}
	}

	/**
	 * 扫描包（class path）下所有的 class 。
	 *
	 * @param filePath      文件绝对路径
	 * @param packageName   包名
	 * @param recursive     true -- 表示扫描所有，包括子目录；false -- 表示只扫描当前包
	 * @param results       将返回所有的 class 文件
	 * @param filter		class 过滤器
	 * @return
	 * @throws ClassNotFoundException 
	 */
	private static void findClassesInPackageByFile(String filePath, String packageName, 
			final boolean recursive, Set<Class<?>> results, ClassFilter filter){

		//获取所有 class 文件 
		ArrayWrapper<File> files = FileUtils.listClassFiles(filePath, recursive);

		files.forEach(file->{
			// 如果是目录 则继续扫描  
			if (file.isDirectory()) {  
				findClassesInPackageByFile(file.getAbsolutePath(), packageName + "." + file.getName(), recursive, results, filter);  
			} else {  
				Class<?> c = loadClassesForFile(packageName, file, filter);
				if(c != null){
					results.add(c);
				}
			}  
		});
		
	}
	
	/**
	 * 载入 class
	 *
	 * @param packageName
	 * @param file
	 * @param filter
	 * @return
	 */
	private static Class<?> loadClassesForFile(String packageName, File file, ClassFilter filter){
		// 如果是java类文件 去掉后面的.class 只留下类名  
		String className = file.getName().substring(0, file.getName().length() - 6);  
		try {  
			//经这里用forName有一些不好，会触发static方法，没有使用classLoader的load干净
			Class<?> result = Thread.currentThread().getContextClassLoader().loadClass(packageName + '.' + className);
			if(filter != null && !filter.test(result)){
				return null;
			}
			return result;
		} catch (ClassNotFoundException e) {  
			FrameworkLogger.error("找不到此类的.class文件:" + file.getName());  
		}  
		return null;
	}
	
	/**
	 * 在 Jar 文件中读取包下的所有 class 文件 
	 *
	 * @param url  			jar 文件 url
	 * @param packageName	包名称
	 * @param recursive		true 查找所有，包括子目录；false 仅查找当前目录。
	 * @param results		找到的结果由 results 传出
	 * @param filter		class 过滤器
	 * @throws Exception 
	 */
	public static void findClassesInPackageByJarFile(URL url, String packageName, 
			boolean recursive, Set<Class<?>> results, ClassFilter filter) 
			throws Exception{
		JarFile jar;
		String packageDirName = packageName.replace('.', '/');
		try{
			jar = ((JarURLConnection) url.openConnection()).getJarFile();
			
			// 从此jar包 得到一个元素集  
			List<JarEntry> entries = Collections.list(jar.entries());
						
			// 进行循环迭代  
			for (JarEntry entry: entries) {  
				// 获取jar里的一个实体 可以是目录 和一些jar包里的其他文件 如META-INF等文件  
				String name = entry.getName();  
				// 如果是以/开头的  
				if (name.charAt(0) == '/') {  
					// 获取后面的字符串  
					name = name.substring(1);  
				}  
				// 如果前半部分和定义的包名相同  
				if (name.startsWith(packageDirName)) {  
					int idx = name.lastIndexOf('/');  
					// 如果以"/"结尾 是一个包  
					if (idx != -1) {  
						// 获取包名 把"/"替换成"."  
						packageName = name.substring(0, idx).replace('/', '.');  
					}  
					// 如果可以迭代下去 并且是一个包  
					if ((idx != -1) || recursive) {  
						// 如果是一个.class文件 而且不是目录  
						if (name.endsWith(".class")	&& !entry.isDirectory()) {  
							// 去掉后面的".class" 获取真正的类名  
							String className = name.substring( packageName.length() + 1, name.length() - 6);  
							try {  
								// 添加到classes
								Class result = Thread.currentThread().getContextClassLoader().loadClass(packageName + '.'	+ className);
								if(filter != null && !filter.test(result))
									continue;
								results.add(result);  
							} catch (ClassNotFoundException e) {  
								e.printStackTrace();  
							}  
						}  
					}  
				}  
			}  
			
		}catch(Exception e){
			throw e;
		}
	}

}
